/*==========================================================================

    uiwin32.cpp  -  Copyright (C) 1993-2005 by Don Cross

    Contains Chenard user interface code specialized
    for 32-bit Windows.

==========================================================================*/

#include <stdio.h>
#include <process.h>
#include <assert.h>
#include <time.h>

#include "chess.h"
#include "gamefile.h"
#include "winchess.h"
#include "ichess.h"
#include "winguich.h"
#include "resource.h"
#include "profiler.h"

#define  REQUEST_BENCHMARK_DATA   0

bool Global_SuggestMoveFlag = false;
ComputerChessPlayer *Global_SuggestMoveThinker = 0;
CRITICAL_SECTION SuggestMoveCriticalSection;        // initialized in WinMain()
bool Global_UserResign = false;
bool Global_SpeakMovesFlag = true;
bool Global_EnableMateAnnounce = true;
extern bool Global_EnableOppTime;
extern int Global_AnalysisType;


ChessUI_win32_gui::ChessUI_win32_gui ( HWND _hwnd ):
    hwnd ( _hwnd ),
    whitePlayerType (DefPlayerInfo::unknownPlayer),
    blackPlayerType (DefPlayerInfo::unknownPlayer),
    whitePlayer (0),
    blackPlayer (0),
    tacticalBenchmarkMode (false),
    benchmarkPlayer (0),
    enableMateAnnounce (Global_EnableMateAnnounce),
    enableOppTime (true)
{
}


ChessUI_win32_gui::~ChessUI_win32_gui()
{
}


ChessPlayer *ChessUI_win32_gui::CreatePlayer ( ChessSide side )
{
    static int doneWhite = 0;

    ChessPlayer *player = 0;
    int searchDepth;
    int searchBias;
    int useSeconds;

    DefPlayerInfo::Type playerType;

    if ( side == SIDE_WHITE )
    {
        doneWhite = 1;
        playerType = DefPlayer.whiteType;
        searchDepth = DefPlayer.whiteThinkTime;
        searchBias = DefPlayer.whiteSearchBias;
        useSeconds = DefPlayer.whiteUseSeconds;
    }
    else
    {
        playerType = DefPlayer.blackType;
        searchDepth = DefPlayer.blackThinkTime;
        searchBias = DefPlayer.blackSearchBias;
        useSeconds = DefPlayer.blackUseSeconds;
    }

    switch ( playerType )
    {
        case DefPlayerInfo::computerPlayer:
        {
            ComputerChessPlayer *puter = new ComputerChessPlayer (*this);
            if ( puter )
            {
                player = puter;
                if ( useSeconds )
                    puter->SetTimeLimit ( searchDepth );
                else
                    puter->SetSearchDepth ( searchDepth );

                puter->SetSearchBias ( searchBias );
                extern bool Global_AllowResignFlag;
                puter->setResignFlag ( Global_AllowResignFlag );
            }
        }
        break;

        case DefPlayerInfo::humanPlayer:
        {
            player = new HumanChessPlayer (*this);
        }
        break;

#if SUPPORT_INTERNET
        case DefPlayerInfo::internetPlayer:
        {
            const InternetConnectionInfo &cinfo =
                (side == SIDE_WHITE) ?
                DefPlayer.whiteInternetConnect :
                DefPlayer.blackInternetConnect;

            extern bool Global_InternetPlayersReady;
            while ( !Global_InternetPlayersReady )
                Sleep(500);

            MessageBox ( hwnd, "Remote connection established...\nready to play!", "", MB_OK | MB_ICONINFORMATION );
            player = new InternetChessPlayer (*this, cinfo);
        }
        break;
#endif  // SUPPORT_INTERNET

#if SUPPORT_NAMED_PIPE
        case DefPlayerInfo::namedPipePlayer:
        {
            const char *serverMachineName =
                (side == SIDE_WHITE) ?
                DefPlayer.whiteServerName :
                DefPlayer.blackServerName;

            player = new NamedPipeChessPlayer (*this, serverMachineName);
        }
        break;
#endif // SUPPORT_NAMED_PIPE
    }

    if ( side == SIDE_WHITE )
        whitePlayerType = playerType;
    else
        blackPlayerType = playerType;

    if ( doneWhite && side==SIDE_BLACK )
    {
        if ( whitePlayerType != DefPlayerInfo::humanPlayer &&
             blackPlayerType == DefPlayerInfo::humanPlayer )
        {
            TheBoardDisplayBuffer.setView(0);
        }

        if ( whitePlayerType == DefPlayerInfo::computerPlayer &&
             blackPlayerType != DefPlayerInfo::computerPlayer )
        {
            ((ComputerChessPlayer *)whitePlayer)->oppTime_SetEnableFlag();
        }

        if ( blackPlayerType == DefPlayerInfo::computerPlayer &&
             whitePlayerType != DefPlayerInfo::computerPlayer )
        {
            ((ComputerChessPlayer *)player)->oppTime_SetEnableFlag();
        }
    }

    if ( side == SIDE_WHITE )
        whitePlayer = player;
    else
        blackPlayer = player;

    return player;
}


static void LoadGameFromFile ( FILE *f, ChessBoard &board )
{
    board.Init();

    Move move;
    UnmoveInfo unmove;

    for(;;)
    {
        int c1 = fgetc(f);
        int c2 = fgetc(f);
        int c3 = fgetc(f);
        int c4 = fgetc(f);
        if ( c4 == EOF )
            break;

        move.source  =  BYTE(c1);
        move.dest    =  BYTE(c2);
        move.score   =  (c4 << 8) | c3;

        if ( sizeof(move.score)>2 && (c4&0x80) )
            move.score |= ((-1) ^ 0xffff);      // perform sign-extend if necessary

        if ( (move.dest & SPECIAL_MOVE_MASK) == SPECIAL_MOVE_EDIT )
        {
            board.EditCommand ( move );
            board.SaveSpecialMove ( move );
        }
        else
            board.MakeMove ( move, unmove );
    }

    Global_MoveUndoPath.resetFromBoard ( board );
}



const char *GetPgnFileStateString (PGN_FILE_STATE state);



PGN_FILE_STATE LoadGameFromPgnFile (FILE *f, ChessBoard &board)     // loads only the first game in the PGN file
{
    PGN_FILE_STATE  state;
    char            movestr [1+MAX_MOVE_STRLEN];
    Move            move;
    UnmoveInfo      unmove;
    PgnExtraInfo    info;

    board.Init();

    while (GetNextPgnMove (f, movestr, state, info)) {
        if (info.fen[0]) {
            bool success = board.SetForsythEdwardsNotation (info.fen);
            if (!success) {
                state = PGN_FILE_STATE_SYNTAX_ERROR;
                break;
            }
        }

        if (board.ScanMove (movestr, move)) {
            board.MakeMove (move, unmove);
        } else {
            state = PGN_FILE_STATE_ILLEGAL_MOVE;
            break;
        }
    }

    if (info.fen[0]) {
        // Special case:  there may have been an edited position, but no moves after that.
        bool success = board.SetForsythEdwardsNotation (info.fen);
        if (!success) {
            state = PGN_FILE_STATE_SYNTAX_ERROR;
        }
    }

    Global_MoveUndoPath.resetFromBoard (board);
    return state;
}


static void LoadGameFromHardcode (
    unsigned long *h,
    int n,
    ChessBoard &board )
{
    board.Init();
    Move move;

    for ( int i=0; i<n; i++ )
    {
        move.source = BYTE ( h[i] & 0xff );
        move.dest   = BYTE ( (h[i] >> 8) & 0xff );
        move.score  = SCORE ( (h[i] >> 16) & 0xffff );
        if ( sizeof(move.score)>2 && (move.score & 0x8000) )
            move.score |= ((-1) ^ 0xffff);      // perform sign-extend if necessary

        if ( (move.dest & SPECIAL_MOVE_MASK) == SPECIAL_MOVE_EDIT )
        {
            board.EditCommand ( move );
            board.SaveSpecialMove ( move );
        }
        else
        {
            UnmoveInfo unmove;
            board.MakeMove ( move, unmove );
        }
    }
}


static void SaveGameToFile (
    FILE *outfile,
    const ChessBoard  &board )
{
    int ply = board.GetCurrentPlyNumber();

    for ( int p=0; p < ply; p++ )
    {
        Move move = board.GetPastMove(p);
        fputc ( move.source, outfile );
        fputc ( move.dest, outfile );
        fputc ( move.score & 0xff, outfile );
        fputc ( (move.score >> 8) & 0xff, outfile );
    }
}



INT32 SuggestThinkTime = 500;   // expressed in centiseconds


BOOL CALLBACK SuggestMove_DlgProc (
    HWND hwnd,
    UINT msg,
    WPARAM wparam,
    LPARAM lparam )
{
    BOOL result = FALSE;
    char buffer [128];
    HWND timeEditBox = GetDlgItem (hwnd, IDC_EDIT_SUGGEST_TIME);

    switch ( msg )
    {
        case WM_INITDIALOG:
        {
            // Remember think time setting from previous suggested moves...

            int length = sprintf ( buffer, "%lg", double(SuggestThinkTime)/100.0 );
            SetWindowText (timeEditBox, buffer);
            SetFocus (timeEditBox);

            // Select the text inside the text box, so the user does not need to hit TAB key to edit it.
            // http://msdn.microsoft.com/en-us/library/bb761661(VS.85).aspx
            SendMessage (timeEditBox, EM_SETSEL, 0 /*starting position*/, length /*one past end position*/);
        }
        break;

        case WM_COMMAND:
        {
            switch ( wparam )
            {
                case IDOK:
                {
                    buffer[0] = '\0';
                    GetWindowText ( timeEditBox, buffer, sizeof(buffer) );
                    double thinkTime = atof(buffer);
                    if ( thinkTime < 0.1 )
                        thinkTime = 0.1;

                    SuggestThinkTime = INT32 ( thinkTime * 100 );
                    EndDialog ( hwnd, IDOK );
                    result = TRUE;
                }
                break;

                case IDCANCEL:
                {
                    EndDialog ( hwnd, IDCANCEL );
                    result = TRUE;
                }
                break;
            }
        }
        break;
    }

    return result;
}


static bool SuggestMove (
    ChessBoard  &board,
    int         &source,
    int         &dest )
{
    if (Global_UI) {
        INT_PTR choice = DialogBox (
            global_hInstance,
            MAKEINTRESOURCE(IDD_REQUEST_SUGGESTION),
            HwndMain,
            DLGPROC(SuggestMove_DlgProc)
        );

        if (choice == IDOK) {
            INT32 timeSpent = 0;
            Move move;
            ComputerChessPlayer thinker (*Global_UI);
            thinker.SetTimeLimit ( SuggestThinkTime );
            thinker.setEnableMoveDisplay (false);
            bool saveMateAnnounce = Global_UI->allowMateAnnounce (false);

            Global_SuggestMoveThinker = &thinker;           // so user can force the thinker to stop thinking early
            thinker.GetMove ( board, move, timeSpent );

            // We use a critical section to avoid a race condition where
            // the user forces the thinker to stop thinking, but the pointer changes to NULL
            // right before the call to Global_SuggestMoveThinker->AbortSearch().
            EnterCriticalSection (&SuggestMoveCriticalSection);
            Global_SuggestMoveThinker = 0;                  // do not let the global code access this local variable any more
            LeaveCriticalSection (&SuggestMoveCriticalSection);

            Global_UI->allowMateAnnounce (saveMateAnnounce);

            char moveString [1 + MAX_MOVE_STRLEN];
            FormatChessMove ( board, move, moveString );

            char forced_mate [64];
            int mate_in_moves = 0;

            if (board.WhiteToMove()) {
                if (move.score >= WON_FOR_WHITE) {
                    if (move.score < WHITE_WINS - WIN_DELAY_PENALTY) {
                        mate_in_moves = 1 + ((WHITE_WINS - int(move.score)) / WIN_DELAY_PENALTY) / 2;
                    }
                }
            } else {
                if (move.score <= WON_FOR_BLACK) {
                    if (move.score > BLACK_WINS + WIN_DELAY_PENALTY) {
                        mate_in_moves = 1 + ((int(move.score) - BLACK_WINS) / WIN_DELAY_PENALTY) / 2;
                    }
                }
            }

            if (mate_in_moves) {
                sprintf (forced_mate, "  [Mate in %d]", mate_in_moves);
            } else {
                forced_mate[0] = '\0';
            }

            char buffer [1024];

            sprintf (
                buffer,
                "After thinking for %0.2lf seconds,\n"
                "the computer suggests the following move:\n\n"
                "     %s%s\n\n"
                "Do you want the computer to make this move for you?",
                (double(timeSpent) / 100.0),
                moveString,
                forced_mate
            );

            int makeMoveChoice = MessageBox (
                HwndMain,
                buffer,
                CHESS_PROGRAM_NAME,
                MB_ICONQUESTION | MB_YESNO
            );

            if (makeMoveChoice == IDYES) {
                Global_UI->DisplayMove ( board, move );
                source = move.source;
                dest = move.dest;
                return true;
            }
        }
    }

    return false;
}


const char *GetPlayerString (DefPlayerInfo::Type type)
{
    switch (type) {
        case DefPlayerInfo::computerPlayer:     return "Computer Player (Chenard)";
        case DefPlayerInfo::humanPlayer:        return "Human Player";
        case DefPlayerInfo::internetPlayer:     return "Remote/Internet Player";
        case DefPlayerInfo::namedPipePlayer:    return "Named Pipe Server";
        default:                                return "?";
    }
}


const char *GetWhitePlayerString ()
{
    const char *WhitePlayerString;
    if (Global_UI) {
        WhitePlayerString = GetPlayerString (Global_UI->queryWhitePlayerType());
    } else {
        WhitePlayerString = "?";
    }
    return WhitePlayerString;
}


const char *GetBlackPlayerString()
{
    const char *BlackPlayerString;
    if (Global_UI) {
        BlackPlayerString = GetPlayerString (Global_UI->queryBlackPlayerType());
    } else {
        BlackPlayerString = "?";
    }
    return BlackPlayerString;
}


bool ProcessChessCommands ( ChessBoard &board, int &source, int &dest )
{
    char msg [1024];
    bool whiteMove = board.WhiteToMove();
    Move edit;
    edit.score = 0;

    bool itIsTimeToCruise = false;
    if ( Global_TacticalBenchmarkFlag && Global_UI )
    {
        const char *explainText =
        "The tactical benchmark will run a series of pre-programmed chess positions "
        "to determine how quickly your computer can perform chess calculations. "
        "After the benchmark is finished, the current chess position will be restored "
        "and you will be able to resume your game. "
        "However, this test may take a long time (4 minutes on a 100 MHz Pentium).\n\n"
        "Do you really want to run the tactical benchmark?";

        Global_TacticalBenchmarkFlag = false;
        int choice = MessageBox ( HwndMain,
            explainText,
            CHESS_PROGRAM_NAME,
            MB_ICONQUESTION | MB_YESNO );

        if ( choice == IDYES )
        {
            double timeElapsed = Global_UI->runTacticalBenchmark();
            if ( timeElapsed > 0.0 )
            {
                const char *resultFormat =
                "Benchmark completed in %0.2lf seconds\n\n"
                "Your computer is %0.2lf times as fast as the author's 100 MHz Pentium."
#ifdef CHENARD_PROFILER
                "\n\ntime data: {%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d}\n"
                "call data: {%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d}\n\n"
#endif

#if REQUEST_BENCHMARK_DATA
                "\n\nIf you feel like it, send email to me <cosinekitty@hotmail.com> with your computer's "
                "benchmark time and your processor type (e.g. 486, Pentium) and speed (MHz). "
                "I want to start collecting these statistics to publish them on the Chenard web page:\n\n"
                "http://www.cosinekitty.com/chenard.html"
#endif
                ;

                sprintf ( msg, resultFormat,
                    timeElapsed,
                    290.0 / timeElapsed
#ifdef CHENARD_PROFILER
                    , ProfilerHitCount[0],
                    ProfilerHitCount[1],
                    ProfilerHitCount[2],
                    ProfilerHitCount[3],
                    ProfilerHitCount[4],
                    ProfilerHitCount[5],
                    ProfilerHitCount[6],
                    ProfilerHitCount[7],
                    ProfilerHitCount[8],
                    ProfilerHitCount[9],

                    ProfilerCallCount[0],
                    ProfilerCallCount[1],
                    ProfilerCallCount[2],
                    ProfilerCallCount[3],
                    ProfilerCallCount[4],
                    ProfilerCallCount[5],
                    ProfilerCallCount[6],
                    ProfilerCallCount[7],
                    ProfilerCallCount[8],
                    ProfilerCallCount[9]
#endif
                );

                MessageBox ( HwndMain, msg, CHESS_PROGRAM_NAME, MB_OK );
                TheBoardDisplayBuffer.update (board);
                TheBoardDisplayBuffer.freshenBoard();
            }
        }
    }
    else if ( Global_UI && Global_SuggestMoveFlag )
    {
        Global_SuggestMoveFlag = false;
        itIsTimeToCruise = SuggestMove ( board, source, dest );
    }
    else if ( Global_BoardEditRequest.getState() != BER_NOTHING )
    {
        if ( Global_GameOverFlag )
        {
            MessageBox ( HwndMain,
                "Please start a new game (File | New) before editing the board",
                CHESS_PROGRAM_NAME,
                MB_ICONEXCLAMATION | MB_OK );
        }
        else
        {
            if (Global_BoardEditRequest.getState() == BER_FEN) {
                // Set entire board to the specified Forsyth Edwards Notation.

                const char *fen = Global_BoardEditRequest.getFen();
                if (board.SetForsythEdwardsNotation (fen)) {
                    TheBoardDisplayBuffer.update (board);
                    // We need to make a NULL move so that legal move list gets regenerated,
                    // *and* the correct player will move,
                    // *and* we will detect end-of-game properly.

                    source = 0;
                    dest = SPECIAL_MOVE_NULL;
                    Global_GameModifiedFlag = true;
                    itIsTimeToCruise = true;
                } else {
                    // There was something wrong with the FEN string we were given.
                    MessageBox (
                        HwndMain,
                        "The Forsyth Edwards Notation was not valid.",
                        CHESS_PROGRAM_NAME,
                        MB_ICONEXCLAMATION | MB_OK
                    );
                }
            } else if (Global_BoardEditRequest.getState() == BER_SQUARE) {
                // Change the contents of a single square.

                SQUARE square;
                int    x;
                int    y;

                Global_BoardEditRequest.getSingleSquareEdit (x, y, square);

                SQUARE prev = board.GetSquareContents (x, y);
                board.SetSquareContents ( square, x, y );
                if (board.PositionIsPossible()) {
                    edit.dest = dest = SPECIAL_MOVE_EDIT | ConvertSquareToNybble(square);
                    edit.source = source = OFFSET(x+2,y+2);
                    board.SaveSpecialMove ( edit );
                    Global_GameModifiedFlag = true;
                    itIsTimeToCruise = true;
                } else {
                    board.SetSquareContents (prev, x, y);

                    MessageBox (
                        HwndMain,
                        "Doing that would create an impossible position.",
                        CHESS_PROGRAM_NAME,
                        MB_ICONEXCLAMATION | MB_OK
                    );
                }
            }
        }

        Global_BoardEditRequest.reset();
    }
    else if ( Global_ClearBoardFlag )
    {
        if ( Global_GameOverFlag )
        {
            MessageBox ( HwndMain,
                "Please start a new game (File | New) before clearing the board",
                CHESS_PROGRAM_NAME,
                MB_ICONEXCLAMATION | MB_OK );
        }
        else
        {
            int choice = MessageBox
               ( HwndMain,
                 "Do you really want to clear the board?",
                 CHESS_PROGRAM_NAME,
                 MB_YESNO | MB_ICONQUESTION | MB_APPLMODAL );

            if ( choice == IDYES )
            {
                board.ClearEverythingButKings();
                itIsTimeToCruise = true;
                edit.source = source = 0;        // signal to clear everything!
                edit.dest = dest = SPECIAL_MOVE_EDIT;
                board.SaveSpecialMove ( edit );
            }
        }

        Global_ClearBoardFlag = false;
    }
    else if ( Global_ResetGameFlag )
    {
        Global_ResetGameFlag = false;

        int choice = IDYES;
        if ( Global_GameModifiedFlag )
        {
            choice = MessageBox (
                HwndMain,
                "Do you really want to start a new game?",
                CHESS_PROGRAM_NAME,
                MB_YESNO | MB_ICONQUESTION | MB_APPLMODAL );
        }

        if ( choice == IDYES )
        {
            board.Init();
            itIsTimeToCruise = true;
            dest = SPECIAL_MOVE_NULL;
            source = 0;
            Global_HaveFilename = false;
            Global_GameModifiedFlag = false;

            if ( Global_UI )
                Global_UI->allowMateAnnounce (Global_EnableMateAnnounce);
        }
    }
    else if ( Global_RedoMoveFlag )
    {
        Global_RedoMoveFlag = false;
        Global_MoveUndoPath.redo ( board );
        TheBoardDisplayBuffer.update ( board );
        Global_GameModifiedFlag = true;

        if ( Global_UI )
        {
            Global_UI->allowMateAnnounce (Global_EnableMateAnnounce);
            Global_UI->DrawBoard ( board );
        }

        if ( board.GameIsOver() )
        {
            dest = SPECIAL_MOVE_NULL;
            source = 0;
            itIsTimeToCruise = true;
        }
    }
    else if ( Global_UndoMoveFlag )
    {
        Global_UndoMoveFlag = false;
        Global_MoveUndoPath.undo ( board );
        TheBoardDisplayBuffer.update ( board );
        Global_GameModifiedFlag = true;

        if ( Global_UI )
        {
            Global_UI->allowMateAnnounce (Global_EnableMateAnnounce);
            Global_UI->DrawBoard ( board );
        }

        // The following lines were added to handle the special case
        // of undoing a move at end-of-game.
        itIsTimeToCruise = true;
        Global_GameOverFlag = false;
    }
    else if ( Global_GameSaveFlag )
    {
         Global_GameSaveFlag = false;
         bool known = false;
         sprintf ( msg, "Cannot save file '%s'", Global_GameFilename );     // get error message ready, just in case

         // Look at the filename extension to determine which kind of file to save:
         const char *ext = strrchr (Global_GameFilename, '.');
         if (ext) {
             FILE *outfile = NULL;
             if (0 == stricmp(ext,".pgn")) {
                 known = true;
                 outfile = fopen ( Global_GameFilename, "wt" );
                 if (outfile != NULL) {
                     SavePortableGameNotation (outfile, board);
                 }
             } else if (0 == stricmp(ext,".gam")) {
                 known = true;
                 outfile = fopen ( Global_GameFilename, "wb" );
                 if (outfile != NULL) {
                     SaveGameToFile ( outfile, board );
                 }
             }
             if (known) {
                 if (outfile == NULL) {
                     MessageBox ( HwndMain, msg, "File Open Error", MB_OK | MB_ICONERROR );
                 } else {
                     fclose (outfile);
                     outfile = NULL;
                     Global_HaveFilename = true;
                     Global_GameModifiedFlag = false;
                 }
             }
             assert (outfile == NULL);
         }
         if (!known) {
             sprintf ( msg, "File '%s' has unknown extension", Global_GameFilename );
             MessageBox ( HwndMain, msg, "File Open Error", MB_OK | MB_ICONERROR );
         }
    }
    else if ( Global_GameOpenFlag )
    {
        Global_GameOpenFlag = false;
        FILE *infile = fopen ( Global_GameFilename, "rb" );
        if ( infile )
        {
            const char *ext = strrchr (Global_GameFilename, '.');
            if (0 == stricmp(ext,".pgn")) {
                PGN_FILE_STATE state = LoadGameFromPgnFile (infile, board);
                if (state != PGN_FILE_STATE_GAMEOVER) {
                    const char *error = GetPgnFileStateString (state);
                    sprintf (msg, "Problem loading pgn file '%s':\n%s", Global_GameFilename, error);
                    MessageBox (HwndMain, msg, "PGN Load Error", MB_OK | MB_ICONERROR);
                }
            } else {
                LoadGameFromFile ( infile, board );
            }
            Global_HaveFilename = true;
            SetWindowText (
                HwndMain,
                board.WhiteToMove() ? TitleBarText_WhitesTurn : TitleBarText_BlacksTurn );

            TheBoardDisplayBuffer.update (board);
            TheBoardDisplayBuffer.freshenBoard();
            Global_GameModifiedFlag = false;

            if ( Global_UI )
                Global_UI->allowMateAnnounce (Global_EnableMateAnnounce);

            // We need to make a NULL move so that legal move list gets regenerated,
            // *and* the correct player will move,
            // *and* we will detect end-of-game properly.

            source = 0;
            dest = SPECIAL_MOVE_NULL;
            itIsTimeToCruise = true;
        }
        else
        {
            sprintf ( msg, "Cannot open file '%s'", Global_GameFilename );
            MessageBox ( HwndMain, msg, "File Open Error", MB_OK | MB_ICONERROR );
        }
    }

    return itIsTimeToCruise;
}


bool ChessUI_win32_gui::ReadMove (
    ChessBoard &board,
    int        &source,
    int        &dest,
    SQUARE     &promIndex )
{
    promIndex = 0;      // indicates that this ChessUI implementation does NOT send back promoted piece, but requires a separate callback

    // Let the client window know we are starting to read
    // a human player's move...
    SendMessage ( hwnd, WM_DDC_ENTER_HUMAN_MOVE, 0, 0 );

    TheBoardDisplayBuffer.startReadingMove ( board.WhiteToMove() );

    while ( TheBoardDisplayBuffer.isReadingMove() )
    {
        Sleep (100);

        if ( Global_AbortFlag )
            return false;   // simulate resignation to abort

        if ( Global_UserResign )
        {
            Global_UserResign = false;
            return false;
        }

        bool itIsTimeToCruise = ProcessChessCommands (board, source, dest);
        if ( itIsTimeToCruise )
        {
            SendMessage ( hwnd, WM_DDC_LEAVE_HUMAN_MOVE, 0, 0 );
            return true;
        }
    }

    TheBoardDisplayBuffer.copyMove ( source, dest );
    SendMessage ( hwnd, WM_DDC_LEAVE_HUMAN_MOVE, 0, 0 );
    return true;
}


SQUARE ChessUI_win32_gui::PromotePawn ( int, ChessSide side )
{
    static volatile SQUARE prom;
    prom = EMPTY;
    PostMessage ( hwnd, WM_DDC_PROMOTE_PAWN, WPARAM(side), LPARAM(&prom) );

    while ( prom == EMPTY )
        Sleep ( 100 );

    return prom;
}


void ChessUI_win32_gui::DrawBoard ( const ChessBoard &board )
{
    TheBoardDisplayBuffer.update ( board );
    TheBoardDisplayBuffer.deselectSquare();
    TheBoardDisplayBuffer.freshenBoard();

    if ( !Global_GameOverFlag )
    {
        ::SetWindowText ( hwnd,
            board.WhiteToMove() ?
            TitleBarText_WhitesTurn :
            TitleBarText_BlacksTurn );
    }

    extern bool Global_EnableBoardBreathe;
    if ( Global_EnableBoardBreathe )
        Sleep ( 100 );   // Helps the board to "breathe"
}


void ChessUI_win32_gui::Resign ( ChessSide iGiveUp, QuitGameReason reason )
{
    static gameReport report;
    report.winner = (iGiveUp == SIDE_WHITE) ? SIDE_BLACK : SIDE_WHITE;
    report.resignation = true;
    report.quitReason = reason;
    PostMessage ( hwnd, WM_DDC_GAME_RESULT, WPARAM(0), LPARAM(&report) );
    //SetWindowText ( hwnd, TitleBarText_GameOver );
}


void ChessUI_win32_gui::ReportEndOfGame ( ChessSide winner )
{
    static gameReport report;
    report.winner = winner;
    report.resignation = false;
    PostMessage ( hwnd, WM_DDC_GAME_RESULT, WPARAM(0), LPARAM(&report) );
    //SetWindowText ( hwnd, TitleBarText_GameOver );
}


void ChessUI_win32_gui::PredictMate ( int n )
{
    if ( !enableMateAnnounce )
        return;

    Sleep ( 200 );
    static volatile int messageProcessed;
    messageProcessed = 0;

    PostMessage (
        hwnd,
        WM_DDC_PREDICT_MATE,
        WPARAM(n),
        LPARAM(&messageProcessed) );

    while ( !messageProcessed )
        Sleep ( 200 );

    enableMateAnnounce = false;   // don't annoy the user further (once is enough!)
}


static void FlashSquare ( int ofs, SQUARE before, SQUARE after )
{
    const int x = XPART(ofs) - 2;
    const int y = YPART(ofs) - 2;

    TheBoardDisplayBuffer.setSquareContents ( x, y, after );
    TheBoardDisplayBuffer.freshenSquare ( x, y );

    for ( int i=0; i < 2; i++ )
    {
        Sleep ( 125 );
        TheBoardDisplayBuffer.setSquareContents ( x, y, before );
        TheBoardDisplayBuffer.freshenSquare ( x, y );
        Sleep ( 125 );
        TheBoardDisplayBuffer.setSquareContents ( x, y, after );
        TheBoardDisplayBuffer.freshenSquare ( x, y );
    }
}


void ChessUI_win32_gui::DisplayMove ( ChessBoard &board, Move move )
{
    if ( tacticalBenchmarkMode )
        return;     // keep move display from messing up our timing

    ChessBeep ( 1400, 50 );

    extern bool Global_AnimateMoves;
    if ( Global_AnimateMoves )
    {
        int ofs1, ofs2;
        move.actualOffsets ( board, ofs1, ofs2 );

        // Show it being picked up...
        FlashSquare ( ofs1, board.GetSquareContents(ofs1), EMPTY );

        // Temporarily make move in board to show it being put down...
        UnmoveInfo unmove;
        board.MakeMove ( move, unmove );
        TheBoardDisplayBuffer.update ( board );
        TheBoardDisplayBuffer.freshenSquare ( XPART(ofs1)-2, YPART(ofs1)-2 );
        FlashSquare ( ofs2, EMPTY, board.GetSquareContents(ofs2) );

        // Undo the move because it's done officially by the ChessGame object.
        board.UnmakeMove ( move, unmove );
    }
}


const unsigned MaxVectorDepth = 3;


void ChessUI_win32_gui::DebugPly ( int depth, ChessBoard &board, Move move )
{
    static int counter = 0;
    static ChessBoard TopBoard, TempBoard;
    static Move CurrentPath [MAX_BESTPATH_DISPLAY];

    if ( depth >= MAX_BESTPATH_DISPLAY )
        return;

    CurrentPath[depth] = move;
    if ( depth == 0 )
    {
        // The parameter 'board' contains the top-level position in the search.
        // Copy it into TempBoard so we have it whenever we need to display
        // move notation for the current path.
        TopBoard = board;
    }

    if ( Global_AnalysisType == 2 )
    {
        const int limit = 15000;
        if ( ++counter > limit )
        {
            // Now it's time to refresh the current path display.

            TempBoard = TopBoard;

			int i;
            for ( i=0; i<=depth; i++ )
            {
                char moveString [64];
                UnmoveInfo unmove;
                FormatChessMove ( TempBoard, CurrentPath[i], moveString );
                TempBoard.MakeMove ( CurrentPath[i], unmove );
                ChessDisplayTextBuffer::SetText ( STATIC_ID_BESTPATH(i), moveString );
            }

            for ( ; i < MAX_BESTPATH_DISPLAY; i++ )
            {
                ChessDisplayTextBuffer::SetText ( STATIC_ID_BESTPATH(i), "" );
            }

            counter = 0;
        }
    }
}


void ChessUI_win32_gui::DebugExit ( int depth, ChessBoard &board, SCORE score )
{
}


void ChessUI_win32_gui::DisplayBestPath (
    const ChessBoard &_board,
    const BestPath &path )
{
    if ( Global_AnalysisType != 1 )
        return;

    // We change the board here, but we put it back the way we find it!
    ChessBoard &board = (ChessBoard &) _board;

    int n = path.depth;
    if ( n > MAX_BESTPATH_DISPLAY )
        n = MAX_BESTPATH_DISPLAY;

    UnmoveInfo unmove [MAX_BESTPATH_DISPLAY];
    bool foundBestMove = true;
    char moveString [64];
    int i, numMovesFound=0;
    Move move [MAX_BESTPATH_DISPLAY];
    for ( i=0; foundBestMove && i < MAX_BESTPATH_DISPLAY; )
    {
        bool xposFlag = false;
        if ( i > n )
        {
            // Try to find best move from transposition table
            const TranspositionEntry *xpos;
            if ( board.WhiteToMove() )
                xpos = ComputerChessPlayer::XposTable->locateWhiteMove(board);
            else
                xpos = ComputerChessPlayer::XposTable->locateBlackMove(board);

            if ( xpos )
            {
                if ( board.isLegal ( xpos->bestReply ) )
                {
                    move[i] = xpos->bestReply;
                    xposFlag = true;
                }
                else
                    foundBestMove = false;
            }
            else
                foundBestMove = false;
        }
        else
        {
            if ( board.isLegal(path.m[i]) )
                move[i] = path.m[i];
            else
                foundBestMove = false;
        }

        if ( foundBestMove )
        {
            FormatChessMove ( board, move[i], moveString );
            if ( i == 0 )
            {
                char temp [256];
                sprintf ( temp, "%-12s %6d", moveString, int(move[i].score) );
                strcpy ( moveString, temp );
            }

            if ( xposFlag )
                strcat ( moveString, "." );

            int numReps = 0;
            bool drawFlag = board.IsDefiniteDraw (&numReps);
            if ( drawFlag )
            {
                if ( numReps >= 3 )
                    strcat ( moveString, " rep" );
                else
                    strcat ( moveString, " draw" );
            }

            board.MakeMove ( move[i], unmove[i] );
            ChessDisplayTextBuffer::SetText ( STATIC_ID_BESTPATH(i), moveString );
            ++i;

            if ( drawFlag )
                break;
        }
    }

    for ( numMovesFound = i--; i >= 0; --i )
        board.UnmakeMove ( move[i], unmove[i] );

    for ( i = numMovesFound; i < MAX_BESTPATH_DISPLAY; ++i )
        ChessDisplayTextBuffer::SetText ( STATIC_ID_BESTPATH(i), "" );
}


void ChessUI_win32_gui::RecordMove (
    ChessBoard  &board,
    Move         move,
    INT32        thinkTime )
{
    int sourceOfs=0, destOfs=0;
    move.actualOffsets ( board, sourceOfs, destOfs );
    TheBoardDisplayBuffer.informMoveCoords (
        XPART(sourceOfs)-2, YPART(sourceOfs)-2,
        XPART(destOfs)-2,   YPART(destOfs)-2 );

    char moveString [MAX_MOVE_STRLEN + 1];
    char displayString [30 + MAX_MOVE_STRLEN + 1];
    int  wm = board.WhiteToMove();

    FormatChessMove ( board, move, moveString );

    UnmoveInfo unmove;
    board.MakeMove ( move, unmove );
    Global_MoveUndoPath.resetFromBoard ( board );
    board.UnmakeMove ( move, unmove );

    if ( Global_SpeakMovesFlag ) {
        Speak (moveString);
    }

    if ( wm )
    {
        sprintf ( displayString, "%d. %s: %s (%0.2lf)",
            1 + board.GetCurrentPlyNumber()/2,
            "White",
            moveString,
            double(thinkTime) / 100.0 );

        ChessDisplayTextBuffer::SetText (
            STATIC_ID_WHITES_MOVE,
            displayString );

        // It will be black's turn real soon now.
        SetWindowText ( hwnd, TitleBarText_BlacksTurn );
    }
    else
    {
        sprintf ( displayString,
            "%s: %s (%0.2lf)",
            "Black",
            moveString,
            double(thinkTime) / 100.0 );

        ChessDisplayTextBuffer::SetText (
            STATIC_ID_BLACKS_MOVE,
            displayString );

        // It will be white's turn real soon now.
        SetWindowText ( hwnd, TitleBarText_WhitesTurn );
    }

    if ( Global_EnableOppTime != enableOppTime )
        enableOppTime = Global_EnableOppTime;

    Global_GameModifiedFlag = true;
}


void ChessUI_win32_gui::DisplayBestMoveSoFar (
    const ChessBoard &board,
    Move bestMove,
    int )
{
    if ( Global_AnalysisType == 2 )
    {
        char moveString [MAX_MOVE_STRLEN + 1];
        char displayString [20 + MAX_MOVE_STRLEN + 1];

        FormatChessMove ( board, bestMove, moveString );
        sprintf ( displayString,
                  "%6d [%s]",
                  int(bestMove.score),
                  moveString );

        ChessDisplayTextBuffer::SetText ( STATIC_ID_CURRENT_MOVE, displayString );
    }

    if ( tacticalBenchmarkMode && bestMove == benchmarkExpectedMove )
        benchmarkPlayer->AbortSearch();
}


void ChessUI_win32_gui::DisplayCurrentMove (
    const ChessBoard &board,
    Move move,
    int level )
{
    if ( Global_AnalysisType != 0 )
    {
        char moveString [MAX_MOVE_STRLEN + 1];
        char displayString [20 + MAX_MOVE_STRLEN + 1];

        FormatChessMove ( board, move, moveString );

        sprintf (
            displayString,
            "%2d: %6d [%s]",
            level,
            int ( move.score ),
            moveString );

        if ( Global_AnalysisType == 1 )
            ChessDisplayTextBuffer::SetText ( STATIC_ID_CURRENT_MOVE, displayString );
    }
}


void ChessUI_win32_gui::ReportSpecial ( const char *msg )
{
    ChessDisplayTextBuffer::SetText (
        STATIC_ID_CURRENT_MOVE,
        msg );
}


void ChessUI_win32_gui::ComputerIsThinking (
    bool                entering,
    ComputerChessPlayer     &thinker )
{
    const char *displayString = "";
    if ( entering )
    {
        if ( thinker.oppTime_QueryInstanceFlag() )
            displayString = "< pondering >";
        else
            displayString = "< thinking >";
    }

    ChessDisplayTextBuffer::SetText (
        STATIC_ID_THINKING,
        displayString );
}


static void FormatDisplayedInteger (unsigned long x, const char *prefix, char *output)
{
    output += sprintf (output, "%s", prefix);

    if (x == 0) {
        sprintf (output, "0");
    } else {
        char r [80];
        int i = 0;
        int n = 0;
        while (x > 0) {
            ++n;
            r[i++] = '0' + (x % 10);
            x /= 10;
            if (n % 3 == 0 && x > 0) {
                r[i++] = ',';
            }
        }
        r[i] = '\0';
        strcpy (output, strrev(r));
    }
}


void ChessUI_win32_gui::ReportComputerStats (
    INT32   thinkTime,
    UINT32  /*nodesVisited*/,
    UINT32  nodesEvaluated,
    UINT32  /*nodesGenerated*/,
    int     /*fwSearchDepth*/,
    UINT32    nvis   [NODES_ARRAY_SIZE],
    UINT32  /*ngen*/ [NODES_ARRAY_SIZE] )
{
    char displayString [80];

    FormatDisplayedInteger (nodesEvaluated, "evaluated=", displayString);
    ChessDisplayTextBuffer::SetText ( STATIC_ID_NODES_EVALUATED, displayString );

	displayString[0] = '\0';     // fallback in case eval/sec cannot be displayed
    if (thinkTime > 5) {
		double nodesPerSecond = double(nodesEvaluated) * 100.0 / double(thinkTime);
        if (nodesPerSecond >= 0.0 && nodesPerSecond < 1.0e+12) {
            FormatDisplayedInteger ((unsigned long) nodesPerSecond, "eval/sec=", displayString);
        }
	}
    ChessDisplayTextBuffer::SetText ( STATIC_ID_NODES_EVALPERSEC, displayString );

    unsigned long elapsedSeconds = thinkTime / 100;
    unsigned long elapsedMinutes = elapsedSeconds / 60;
    elapsedSeconds %= 60;
    unsigned long elapsedHours = elapsedMinutes / 60;
    elapsedMinutes %= 60;
    sprintf (displayString, "time=%lu:%02lu:%02lu", elapsedHours, elapsedMinutes, elapsedSeconds);
    ChessDisplayTextBuffer::SetText (STATIC_ID_ELAPSED_TIME, displayString);

    if ( nvis )
    {
        int limit = MAX_BESTPATH_DISPLAY;
        if ( limit > NODES_ARRAY_SIZE )
            limit = NODES_ARRAY_SIZE;

        for ( int i=1; i < limit; ++i )
        {
            ChessDisplayTextBuffer *tb = ChessDisplayTextBuffer::Find (STATIC_ID_BESTPATH(i));

            if ( tb )
            {
                const char *moveString = tb->queryText();
                char buffer [64];
                int k;
                for ( k=0; moveString[k] && k<12; ++k )
                    buffer[k] = moveString[k];

                for ( ; k<13; ++k )
                    buffer[k] = ' ';

                sprintf ( buffer+k, "%9lu", (unsigned long)(nvis[i]) );
                tb->setText ( buffer );
            }
        }
    }

    extern bool Global_AllowResignFlag;
    if ( whitePlayerType == DefPlayerInfo::computerPlayer && whitePlayer )
        ((ComputerChessPlayer *)whitePlayer)->setResignFlag ( Global_AllowResignFlag );

    if ( blackPlayerType == DefPlayerInfo::computerPlayer && blackPlayer )
        ((ComputerChessPlayer *)blackPlayer)->setResignFlag ( Global_AllowResignFlag );
}


void ChessUI_win32_gui::forceMove()
{
    if ( whitePlayer && whitePlayerType == DefPlayerInfo::computerPlayer ) {
        ((ComputerChessPlayer *)whitePlayer)->AbortSearch();
    }

    if ( blackPlayer && blackPlayerType == DefPlayerInfo::computerPlayer ) {
        ((ComputerChessPlayer *)blackPlayer)->AbortSearch();
    }

    EnterCriticalSection (&SuggestMoveCriticalSection);     // critical section prevents Global_SuggestMoveThinker changing to NULL in other thread while we are using it.
    if (Global_SuggestMoveThinker) {
        Global_SuggestMoveThinker->AbortSearch();
    }
    LeaveCriticalSection (&SuggestMoveCriticalSection);
}


void ChessUI_win32_gui::setWhiteThinkTime ( INT32 hundredthsOfSeconds )
{
    if ( whitePlayerType == DefPlayerInfo::computerPlayer &&
         whitePlayer && hundredthsOfSeconds >= 10 )
    {
        ComputerChessPlayer *puter = (ComputerChessPlayer *)whitePlayer;
        puter->SetTimeLimit ( hundredthsOfSeconds );
    }
}


void ChessUI_win32_gui::setBlackThinkTime ( INT32 hundredthsOfSeconds )
{
    if ( blackPlayerType == DefPlayerInfo::computerPlayer &&
         blackPlayer && hundredthsOfSeconds >= 10 )
    {
        ComputerChessPlayer *puter = (ComputerChessPlayer *)blackPlayer;
        puter->SetTimeLimit ( hundredthsOfSeconds );
    }
}


INT32 ChessUI_win32_gui::queryWhiteThinkTime() const
{
    if ( whitePlayerType == DefPlayerInfo::computerPlayer && whitePlayer )
    {
        const ComputerChessPlayer *puter = (const ComputerChessPlayer *)whitePlayer;
        return puter->QueryTimeLimit();
    }

    return 0;
}


INT32 ChessUI_win32_gui::queryBlackThinkTime() const
{
    if ( blackPlayerType == DefPlayerInfo::computerPlayer && blackPlayer )
    {
        const ComputerChessPlayer *puter = (const ComputerChessPlayer *)blackPlayer;
        return puter->QueryTimeLimit();
    }

    return 0;
}


void ChessUI_win32_gui::NotifyUser ( const char *message )
{
    MessageBox ( HwndMain, message, CHESS_PROGRAM_NAME, MB_OK | MB_ICONINFORMATION );
}


//-------------------------- benchmark stuff ------------------------------


/*
   hardcode varok.gam varok.cpp VarokGame -U -x
*/

static unsigned long VarokGame[] =
{0x4129L,0x5B74L,0x3720L,0xFFFD586FL,0x3226L,0x244D65L,0x362AL,0xFFFD5C68L,
0x291BL,0xFFED6570L,0x3F27L,0xFFE74A62L,0x4B3FL,0xFFED6258L,0x3E32L,
0xFFD86873L,0x4028L,0xFFD5B072L,0x321CL,0xFFDD5864L,0x584BL,0xFFC75862L,
0x331DL,0xFFCF7065L,0x4D40L,0xFFF54D5BL,0x4B1FL,0xFFFB5A70L,0x5037L,
0xC6571L,0xB01EL,0x147173L,0x1B1AL,0x156F6EL};


double ChessUI_win32_gui::runTacticalBenchmark()
{
    StartProfiler();
    tacticalBenchmarkMode = true;
    bool saveMateAnnounce = enableMateAnnounce;
    enableMateAnnounce = false;

    // Create a temporary chess board.
    ChessBoard tboard;

    // Create a temporary chess player to think about the position
    benchmarkPlayer = new ComputerChessPlayer (*this);

    INT32 timeSpent;
    double totalTime = 0.0;
    Move move;
    UnmoveInfo unmove;

    LoadGameFromHardcode (
        VarokGame, sizeof(VarokGame)/sizeof(VarokGame[0]), tboard );
    benchmarkExpectedMove.source = 0;
    benchmarkExpectedMove.dest = 0;
    TheBoardDisplayBuffer.update (tboard);
    TheBoardDisplayBuffer.freshenBoard();
    benchmarkPlayer->SetSearchDepth (4);
    benchmarkPlayer->ResetHistoryBuffers();
    benchmarkPlayer->GetMove ( tboard, move, timeSpent );
    totalTime += (0.01 * timeSpent);

    // Now just search a neutral but very complex midgame position to a fixed depth.
    // We do not expect a particular move; we just want to see how long a fixed depth takes.
    tboard.Init();
    tboard.ClearEverythingButKings();
    tboard.SetSquareContents ( WKING, 6, 0 );
    tboard.SetSquareContents ( BKING, 6, 7 );
    tboard.SetSquareContents ( WROOK, 0, 0 );
    tboard.SetSquareContents ( WROOK, 5, 0 );
    tboard.SetSquareContents ( WPAWN, 0, 1 );
    tboard.SetSquareContents ( WPAWN, 1, 1 );
    tboard.SetSquareContents ( WPAWN, 2, 1 );
    tboard.SetSquareContents ( WQUEEN, 4, 1 );
    tboard.SetSquareContents ( WPAWN, 5, 1 );
    tboard.SetSquareContents ( WPAWN, 6, 1 );
    tboard.SetSquareContents ( WPAWN, 7, 1 );
    tboard.SetSquareContents ( WBISHOP, 1, 2 );
    tboard.SetSquareContents ( WKNIGHT, 2, 2 );
    tboard.SetSquareContents ( WPAWN, 3, 2 );
    tboard.SetSquareContents ( WKNIGHT, 5, 2 );
    tboard.SetSquareContents ( WPAWN, 4, 3 );
    tboard.SetSquareContents ( BBISHOP, 2, 4 );
    tboard.SetSquareContents ( BPAWN, 4, 4 );
    tboard.SetSquareContents ( WBISHOP, 6, 4 );
    tboard.SetSquareContents ( BPAWN, 0, 5 );
    tboard.SetSquareContents ( BKNIGHT, 2, 5 );
    tboard.SetSquareContents ( BPAWN, 3, 5 );
    tboard.SetSquareContents ( BKNIGHT, 5, 5 );
    tboard.SetSquareContents ( BPAWN, 1, 6 );
    tboard.SetSquareContents ( BPAWN, 2, 6 );
    tboard.SetSquareContents ( BBISHOP, 3, 6 );
    tboard.SetSquareContents ( BPAWN, 5, 6 );
    tboard.SetSquareContents ( BPAWN, 6, 6 );
    tboard.SetSquareContents ( BPAWN, 7, 6 );
    tboard.SetSquareContents ( BROOK, 0, 7 );
    tboard.SetSquareContents ( BQUEEN, 3, 7 );
    tboard.SetSquareContents ( BROOK, 4, 7 );
    benchmarkExpectedMove.source = 0;
    benchmarkExpectedMove.dest = 0;
    TheBoardDisplayBuffer.update (tboard);
    TheBoardDisplayBuffer.freshenBoard();
    benchmarkPlayer->SetSearchDepth (4);
    benchmarkPlayer->ResetHistoryBuffers();
    benchmarkPlayer->GetMove ( tboard, move, timeSpent );
    totalTime += (0.01 * timeSpent);

    // Now do a little endgame position
    tboard.Init();
    tboard.ClearEverythingButKings();
    tboard.SetSquareContents ( WPAWN, 0, 1 );
    tboard.SetSquareContents ( WPAWN, 1, 1 );
    tboard.SetSquareContents ( WKING, 5, 3 );
    tboard.SetSquareContents ( WKNIGHT, 6, 3 );
    tboard.SetSquareContents ( BPAWN, 3, 4 );
    tboard.SetSquareContents ( WPAWN, 4, 4 );
    tboard.SetSquareContents ( BKING, 4, 5 );
    tboard.SetSquareContents ( BPAWN, 0, 6 );
    tboard.SetSquareContents ( BPAWN, 1, 6 );
    tboard.SetSquareContents ( BBISHOP, 3, 6 );
    TheBoardDisplayBuffer.update (tboard);
    TheBoardDisplayBuffer.freshenBoard();
    benchmarkExpectedMove.source = 0;
    benchmarkExpectedMove.dest = 0;
    benchmarkPlayer->SetSearchDepth (7);
    benchmarkPlayer->ResetHistoryBuffers();
    benchmarkPlayer->GetMove ( tboard, move, timeSpent );
    totalTime += (0.01 * timeSpent);

    // zugzwang test:  Smyslov v Kasparov, Vilnius 1984, game 12.
    tboard.Init();
    tboard.ClearEverythingButKings();
    tboard.SetSquareContents ( WKING, 7, 1 );
    tboard.SetSquareContents ( BKING, 6, 6 );
    tboard.SetSquareContents ( WBISHOP, 4, 1 );
    tboard.SetSquareContents ( WPAWN, 0, 2 );
    tboard.SetSquareContents ( WKNIGHT, 3, 2 );
    tboard.SetSquareContents ( BQUEEN, 4, 2 );
    tboard.SetSquareContents ( WPAWN, 6, 2 );
    tboard.SetSquareContents ( WPAWN, 1, 3 );
    tboard.SetSquareContents ( WPAWN, 4, 3 );
    tboard.SetSquareContents ( WQUEEN, 6, 3 );
    tboard.SetSquareContents ( BPAWN, 1, 4 );
    tboard.SetSquareContents ( BPAWN, 6, 4 );
    tboard.SetSquareContents ( WPAWN, 7, 4 );
    tboard.SetSquareContents ( BPAWN, 0, 5 );
    tboard.SetSquareContents ( WPAWN, 4, 5 );
    tboard.SetSquareContents ( BPAWN, 7, 5 );
    tboard.SetSquareContents ( BKNIGHT, 4, 6 );
    tboard.SetSquareContents ( BROOK, 5, 7 );
    move.source = OFFSET(9,3);
    move.dest = OFFSET(8,3);
    tboard.MakeMove ( move, unmove );
    benchmarkExpectedMove.source = 0;
    benchmarkExpectedMove.dest = 0;
    benchmarkPlayer->SetSearchDepth (4);
    TheBoardDisplayBuffer.update (tboard);
    TheBoardDisplayBuffer.freshenBoard();
    benchmarkPlayer->ResetHistoryBuffers();
    benchmarkPlayer->GetMove ( tboard, move, timeSpent );
    totalTime += (0.01 * timeSpent);

    // Late midgame, with highly mobile pieces
    tboard.Init();
    tboard.ClearEverythingButKings();
    tboard.SetSquareContents ( WKING, 6, 0 );
    tboard.SetSquareContents ( WROOK, 3, 0 );
    tboard.SetSquareContents ( WROOK, 4, 0 );
    tboard.SetSquareContents ( WPAWN, 0, 1 );
    tboard.SetSquareContents ( WPAWN, 1, 1 );
    tboard.SetSquareContents ( WBISHOP, 2, 1 );
    tboard.SetSquareContents ( WPAWN, 5, 1 );
    tboard.SetSquareContents ( WPAWN, 6, 1 );
    tboard.SetSquareContents ( WPAWN, 2, 2 );
    tboard.SetSquareContents ( WQUEEN, 6, 2 );
    tboard.SetSquareContents ( WPAWN, 7, 2 );
    tboard.SetSquareContents ( WKNIGHT, 3, 3 );
    tboard.SetSquareContents ( BPAWN, 1, 5 );
    tboard.SetSquareContents ( BPAWN, 4, 5 );
    tboard.SetSquareContents ( BQUEEN, 5, 5 );
    tboard.SetSquareContents ( BPAWN, 6, 5 );
    tboard.SetSquareContents ( BPAWN, 0, 6 );
    tboard.SetSquareContents ( BBISHOP, 1, 6 );
    tboard.SetSquareContents ( BPAWN, 5, 6 );
    tboard.SetSquareContents ( BBISHOP, 6, 6 );
    tboard.SetSquareContents ( BKING, 0, 7 );
    tboard.SetSquareContents ( BROOK, 2, 7 );
    tboard.SetSquareContents ( BROOK, 4, 7 );
    benchmarkExpectedMove.source = 0;
    benchmarkExpectedMove.dest = 0;
    benchmarkPlayer->SetSearchDepth (4);
    TheBoardDisplayBuffer.update (tboard);
    TheBoardDisplayBuffer.freshenBoard();
    benchmarkPlayer->ResetHistoryBuffers();
    benchmarkPlayer->GetMove ( tboard, move, timeSpent );
    totalTime += (0.01 * timeSpent);

    // Complex queen's gambit declined opening position
    tboard.Init();
    tboard.ClearEverythingButKings();
    tboard.SetSquareContents ( WKING, 6, 0 );
    tboard.SetSquareContents ( WROOK, 0, 0 );
    tboard.SetSquareContents ( WQUEEN, 3, 0 );
    tboard.SetSquareContents ( WROOK, 5, 0 );
    tboard.SetSquareContents ( WPAWN, 1, 1 );
    tboard.SetSquareContents ( WPAWN, 5, 1 );
    tboard.SetSquareContents ( WPAWN, 6, 1 );
    tboard.SetSquareContents ( WPAWN, 7, 1 );
    tboard.SetSquareContents ( WPAWN, 0, 2 );
    tboard.SetSquareContents ( WKNIGHT, 2, 2 );
    tboard.SetSquareContents ( WBISHOP, 3, 2 );
    tboard.SetSquareContents ( WPAWN, 4, 2 );
    tboard.SetSquareContents ( WKNIGHT, 5, 2 );
    tboard.SetSquareContents ( WPAWN, 2, 3 );
    tboard.SetSquareContents ( WPAWN, 3, 3 );
    tboard.SetSquareContents ( BPAWN, 3, 4 );
    tboard.SetSquareContents ( WBISHOP, 6, 4 );
    tboard.SetSquareContents ( BPAWN, 1, 5 );
    tboard.SetSquareContents ( BKNIGHT, 2, 5 );
    tboard.SetSquareContents ( BPAWN, 4, 5 );
    tboard.SetSquareContents ( BKNIGHT, 5, 5 );
    tboard.SetSquareContents ( BPAWN, 0, 6 );
    tboard.SetSquareContents ( BBISHOP, 1, 6 );
    tboard.SetSquareContents ( BPAWN, 2, 6 );
    tboard.SetSquareContents ( BBISHOP, 4, 6 );
    tboard.SetSquareContents ( BPAWN, 5, 6 );
    tboard.SetSquareContents ( BPAWN, 6, 6 );
    tboard.SetSquareContents ( BPAWN, 7, 6 );
    tboard.SetSquareContents ( BKING, 6, 7 );
    tboard.SetSquareContents ( BROOK, 4, 7 );
    tboard.SetSquareContents ( BQUEEN, 3, 7 );
    tboard.SetSquareContents ( BROOK, 0, 7 );
    benchmarkExpectedMove.source = 0;
    benchmarkExpectedMove.dest = 0;
    benchmarkPlayer->SetSearchDepth (4);
    TheBoardDisplayBuffer.update (tboard);
    TheBoardDisplayBuffer.freshenBoard();
    benchmarkPlayer->ResetHistoryBuffers();
    benchmarkPlayer->GetMove ( tboard, move, timeSpent );
    totalTime += (0.01 * timeSpent);

    tacticalBenchmarkMode = false;
    enableMateAnnounce = saveMateAnnounce;
    delete benchmarkPlayer;
    benchmarkPlayer = 0;

    StopProfiler();

    return totalTime;
}


void ChessUI_win32_gui::SetAdHocText (
    int index,
    const char *format,
    ... )
{
    if ( index < 0 || index > 3 )
        return;

    char buffer [512];
    va_list argptr;
    va_start ( argptr, format );
    int numChars = vsprintf ( buffer, format, argptr );
    va_end ( argptr );

    if ( index < 2 )
        ChessDisplayTextBuffer::SetText ( STATIC_ID_ADHOC1 + index, buffer );
    else if ( index == 2 )
    {
        strcpy ( TitleBarText_WhitesTurn, "White's Turn - " );
        strcat ( TitleBarText_WhitesTurn, buffer );

        strcpy ( TitleBarText_BlacksTurn, "Black's Turn - " );
        strcat ( TitleBarText_BlacksTurn, buffer );

        strcpy ( TitleBarText_GameOver, buffer );
    }
    else if ( index == 3 )
    {
        ::SetWindowText ( hwnd, buffer );
    }
}


#if ENABLE_OPP_TIME
//----------------- thinking on opponent's time ------------------------

const uintptr_t INVALID_THREAD = (uintptr_t)(-1);

static uintptr_t OppTimeThreadID = INVALID_THREAD;


void OppTimeThreadFunc ( void *data )
{
    ChessBoard board;
    OppTimeParms *parms = (OppTimeParms *)data;
    for(;;)
    {
        // Wait state...
        while ( !parms->wakeUpSignal )
            Sleep(50);

        board = *(parms->inBoard);

        parms->finished     = false;
        parms->wakeUpSignal = false;   // signal caller that board is safely copied
        parms->foundMove    = false;

        UnmoveInfo unmove;
        if ( board.isLegal(parms->myMove_current) )
        {
            board.MakeMove ( parms->myMove_current, unmove );
            if ( board.isLegal(parms->oppPredicted_current) )
            {
                board.MakeMove ( parms->oppPredicted_current, unmove );
                parms->packedNextBoard = board;
                parms->oppThinker->SetTimeLimit (360000);   // one hour

                parms->foundMove = parms->oppThinker->GetMove (
                    board,
                    parms->myMove_next,
                    parms->timeSpent );

                parms->oppPredicted_next = parms->oppThinker->getPredictedOpponentMove();
            }
        }

        parms->finished = true;    // do this LAST!!!
    }
}

bool ChessUI_win32_gui::oppTime_startThinking (
    const ChessBoard    &currentPosition,
    Move                myMove,
    Move                predictedOpponentMove )
{
    if ( !enableOppTime )
        return false;

    if ( !oppTimeParms.oppThinker )
    {
        oppTimeParms.oppThinker = new ComputerChessPlayer (*this);
        if ( !oppTimeParms.oppThinker )
            return false;

        oppTimeParms.oppThinker->oppTime_SetInstanceFlag();
        oppTimeParms.oppThinker->setEnableMoveDisplay (false);
        oppTimeParms.oppThinker->SetSearchBias(1);      // allow randomized search
    }

    // If the thread needs to be started, try to start it...
    if ( OppTimeThreadID == INVALID_THREAD )
    {
        oppTimeParms.wakeUpSignal = false;     // tell thread to wait for work
        OppTimeThreadID = _beginthread ( OppTimeThreadFunc, 0, &oppTimeParms );
    }

    // If the thread is up and running, use it...
    if ( OppTimeThreadID != INVALID_THREAD )
    {
        oppTimeParms.inBoard                =   &currentPosition;
        oppTimeParms.myMove_current         =   myMove;
        oppTimeParms.oppPredicted_current   =   predictedOpponentMove;
        oppTimeParms.finished               =   false;
        oppTimeParms.timeStartedThinking    =   ChessTime();
        oppTimeParms.wakeUpSignal           =   true;  // must do this LAST!!!

        // Wait for OppTimeThreadFunc to get a safe copy of the board before we return...
        while ( oppTimeParms.wakeUpSignal )
            Sleep(50);

        return true;
    }

    return false;
}


bool ChessUI_win32_gui::oppTime_finishThinking (
    const ChessBoard    &newPosition,
    INT32               timeToThink,
    INT32               &timeSpent,
    Move                &myResponse,
    Move                &predictedOpponentMove )
{
    if (OppTimeThreadID != INVALID_THREAD && oppTimeParms.oppThinker) {
        if (enableOppTime) {
            bool predictedCorrectly = (oppTimeParms.packedNextBoard == newPosition);
            if (predictedCorrectly) {
                INT32 timeAlreadySpent = ChessTime() - oppTimeParms.timeStartedThinking;
                INT32 extraTime = timeToThink - timeAlreadySpent;
                if (extraTime < 20) {
                    oppTimeParms.oppThinker->AbortSearch();    
                } else {
                    oppTimeParms.oppThinker->SetTimeLimit (extraTime);
                }
            } else {
                oppTimeParms.oppThinker->AbortSearch();
            }

            while (!oppTimeParms.finished) {
                Sleep(50);
            }

            if (predictedCorrectly && oppTimeParms.foundMove) {
                myResponse              =   oppTimeParms.myMove_next;
                predictedOpponentMove   =   oppTimeParms.oppPredicted_next;
                if (((ChessBoard &)newPosition).isLegal(myResponse)) {
                    // We're on a roll!  Keep thinking...
                    timeSpent = ChessTime() - oppTimeParms.timeStartedThinking;
                    oppTime_startThinking (newPosition, myResponse, predictedOpponentMove);
                    return true;
                }
            }
        } else {
            // Getting here means opp time was enabled, but then turned off later.
            // Cancel any pondering that may be taking place to avoid burning up CPU time needlessly.
            oppTime_abortSearch();
        }
    }

    return false;
}


void ChessUI_win32_gui::oppTime_abortSearch()
{
    if (OppTimeThreadID != INVALID_THREAD && oppTimeParms.oppThinker && !oppTimeParms.finished) {
        oppTimeParms.oppThinker->AbortSearch();
        while (!oppTimeParms.finished) {
            Sleep(50);
        }
    }
}


//----------------------------------------------------------------------
#endif  /* ENABLE_OPP_TIME */
